﻿using System;
using System.Collections;
using System.Collections.Generic;
using Silver3D.Math;

namespace Silver3D.Graphics
{
    /// <summary>
    /// A utility class to enumerate all instances of a certain type on or below a given Node.
    /// Also keeps track of the current instance model-view matrix.
    /// </summary>
    /// <typeparam name="T">An EntityInstance based class - the type to enumerate.</typeparam>
    public class NodeIterator<T> : IEnumerable<T>
        where T : EntityInstance
    {
        /// <summary>
        /// The root Node of the iterator.
        /// </summary>
        public Node Root { get; private set; }

        /// <summary>
        /// The view matrix of the iterator.
        /// </summary>
        public Matrix44 View { get; private set; }

        /// <summary>
        /// The current model-view matrix associated with the current instance.
        /// </summary>
        public Matrix44 ModelView { get; private set; }

        /// <summary>
        /// Creates a scene Node iterator.
        /// </summary>
        /// <param name="root">The starting point of the enumeration.</param>
        /// <param name="viewMatrix">The current camera view matrix used to generate the model-view matrix.</param>
        public NodeIterator(Node root, Matrix44 viewMatrix)
        {
            if (root == null)
                throw new ArgumentNullException("root");

            Root = root;
            View = viewMatrix;
        }

        /// <summary>
        /// Iterative enumeration of all the instances of type T starting from the Root Node.
        /// </summary>
        /// <returns>The enumerator interface.</returns>
        public IEnumerator<T> GetEnumerator()
        {
            Queue<Node> nodes = new Queue<Node>();
            Queue<Matrix44> matrices = new Queue<Matrix44>();
            nodes.Enqueue(Root);
            matrices.Enqueue(Root.ToMatrix() * View);

            while (nodes.Count > 0)
            {
                Node n = nodes.Dequeue();
                Matrix44 m = matrices.Dequeue();

                for (int i = n.ChildCount - 1; i >= 0; ++i)
                {
                    Node c = n.GetChild(i);
                    nodes.Enqueue(c);
                    matrices.Enqueue(m * n.ToMatrix());
                }

                for (int i = 0; i < n.InstanceCount; ++i)
                {
                    EntityInstance ei = n.GetInstance(i);
                    if (ei is T)
                    {
                        this.ModelView = m;
                        yield return (ei as T);
                    }
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
